SpringBoot3 Web容器配置指南

#springboot #tomcat #undertow #性能调优

总结
  • Spring Boot 3 默认 Tomcat,高并发低内存可换 Undertow,Java 21+ 开虚拟线程更值得考虑
  • 线程数从保守值开始压测,不要套公式;IO 密集可多配,CPU 密集接近核心数就够
  • 任务结束务必 remove() ThreadLocal,线程池复用会导致数据污染

1. 先选容器

Spring Boot 3 内置三种 Web 容器,默认是 Tomcat,也可以换成 Undertow。

容器 特点 适合场景
Tomcat 成熟稳定,生态好,默认选择 大部分业务场景
Undertow 更轻量,内存占用低,非阻塞 IO 高并发 API、对内存敏感
Netty 响应式编程专用(WebFlux) 流式、响应式场景

没有特殊需求就用 Tomcat,想要更低内存占用可以换 Undertow。两者在普通业务场景下性能差距感知不明显,换容器不是性能优化的首选手段。

Java 21 + Spring Boot 3.2 之后,开启虚拟线程是更值得考虑的优化方向(见第 4 节)。

2. Tomcat 配置

2.1 基本配置

server:
  port: 8080
  tomcat:
    threads:
      max: 400          # 最大工作线程数,默认 200
      min-spare: 20     # 最小空闲线程数
    max-connections: 8192   # 最大连接数,默认 8192
    accept-count: 100       # 等待队列长度,超出直接拒绝
    connection-timeout: 20000  # 连接超时,单位 ms

注意:Spring Boot 3.x 线程数配置改到了 threads.max,老的 max-threads 写法不生效。

2.2 线程数怎么定?

不要套公式,从保守值开始压测。一般参考:

服务器规格 max 推荐起点 说明
2 核 4G 100~200 先压测再调
4 核 8G 200~400 先压测再调
8 核 16G 400~800 先压测再调

线程不是越多越好,线程过多会导致频繁上下文切换,CPU 调度开销反而成为瓶颈。每个线程还会占用约 1MB 栈内存,800 个线程光栈就 800MB。

max-connections 可以远大于 max-threads,因为大部分连接处于等待 IO 状态,只有真正在处理请求时才占用线程。

2.3 IO 密集 vs CPU 密集

IO 密集型(大量数据库查询、外部接口调用):线程大部分时间在等待,可以适当多配线程,让 CPU 有事可做。

CPU 密集型(大量计算、图像处理):线程数接近 CPU 核心数就够了,多了反而增加切换开销。

3. 换成 Undertow

排除 Tomcat 依赖,引入 Undertow:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

配置:

server:
  undertow:
    threads:
      io: 4          # IO 线程数,建议等于 CPU 核心数
      worker: 128    # 工作线程数,处理业务逻辑
    buffer-size: 1024
    direct-buffers: true  # 使用堆外内存,减少 GC 压力

Undertow 的 IO 线程负责接收连接和读写数据,worker 线程负责处理业务逻辑。IO 线程数一般等于 CPU 核心数就够了。

4. 虚拟线程(Java 21+)

Spring Boot 3.2 支持虚拟线程,开启方式:

spring:
  threads:
    virtual:
      enabled: true

开启后 Tomcat 会用虚拟线程处理每个请求,创建和切换开销极低,可以轻松支撑数万并发。IO 密集型场景下效果最明显,max-threads 的限制基本可以忽略了。

虚拟线程不适合 CPU 密集型任务,那种场景还是用平台线程。

5. 监控线程池

@Component
@Slf4j
public class TomcatThreadPoolMonitor {

    @Autowired
    private TomcatWebServer tomcatWebServer;

    @Scheduled(fixedRate = 30000)
    public void monitor() {
        Connector connector = tomcatWebServer.getTomcat().getConnector();
        AbstractProtocol<?> protocol = (AbstractProtocol<?>) connector.getProtocolHandler();
        log.info("Tomcat 线程池 - 当前: {}, 活跃: {}, 最大: {}, 连接数: {}",
            protocol.getCurrentThreadCount(),
            protocol.getCurrentThreadsBusy(),
            protocol.getMaxThreads(),
            protocol.getConnectionCount());
    }
}

关注几个指标:

相关链接